package com.google.code.iproc.util;

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * Rozsah mezi dvema celymi cisly.
 * @author jdvorak
 *
 */
public class IntegerRange implements Iterable<Integer> {
	
	final int min;
	final int max;
	
	/**
	 * Novy rozsah s danymi mezemi.
	 * @param min
	 * @param max
	 */
	public IntegerRange( int min, int max ) {
		super();
		this.min = min;
		this.max = max;
	}
	
	/**
	 * Vyda dolni mez rozsahu.
	 * @return
	 */
	public int getMin() {
		return min;
	}
	
	/**
	 * Vyda horni mez rozsahu.
	 * @return
	 */
	public int getMax() {
		return max;
	}

	/**
	 * Vyda pocet prvku v rozsahu.
	 * @return
	 */
	public int size() {
		return max - min + 1;
	}
	
	/**
	 * Rekne, zda tento rozsah je prazdny.
	 * @return
	 */
	public boolean isEmpty() {
		return ( min > max );
	}
	
	/**
	 * Rekne, zda dane cislo patri do rozsahu.
	 * @param x
	 * @return
	 */
	public boolean contains( int x ) {
		return ( x >= min ) && ( x <= max );
	}

	/**
	 * Iterator pres cely rozsah.
	 */
	public Iterator<Integer> iterator() {
		return new Iterator<Integer>() {

			int current = min;

			/**
			 * @see Iterator.hasNext()
			 */
			public boolean hasNext() {
				return ( current <= max );
			}

			/**
			 * @see Iterator.next()
			 */
			public Integer next() {
				if ( current > max ) {
					throw new NoSuchElementException();
				}
				return current++;
			}

			/**
			 * @throws UnsupportedOperationException
			 */
			public void remove() {
				throw new UnsupportedOperationException();
			}

		};
	}

	/**
	 * Vyda rozsah posunuty o dane cislo.
	 * @param offset
	 * @return
	 */
	public IntegerRange shift( final int offset ) {
		return ( offset != 0 ) ? new IntegerRange( offset + min, offset + max ) : this;
	}

	@Override
	public String toString() {
		return String.valueOf( min ) + ".." + String.valueOf( max );
	}
	
	@Override
	public boolean equals( final Object o ) {
		if ( o instanceof IntegerRange ) {
			final IntegerRange that = (IntegerRange) o;
			return ( this.min == that.min ) && ( this.max == that.max );
		}
		return false;
	}
	
	@Override
	public int hashCode() {
		return 37 * min + max;
	}

	/**
	 * The predicate is true iff the given integer lies in this range.
	 */
	public boolean accept( final Integer x ) {
		return ( x == null ) ? false : contains( x );
	}

	/**
	 * Produces the intersection of this range with the other one.
	 * @param r2
	 * @return
	 */
	public IntegerRange intersection( final IntegerRange r2 ) {
		return new IntegerRange( Math.max( getMin(), r2.getMin() ),
		                         Math.min( getMax(), r2.getMax() ) );
	}
	
	/**
	 * Produces the union of this range with the other one.
	 * @param r2
	 * @return array of zero, one, or two IntegerRanges
	 */
	public IntegerRange[] union( final IntegerRange r2 ) {
		if ( isEmpty() ) {
			return ( r2.isEmpty() ) ? new IntegerRange[0] : new IntegerRange[] { r2 };
		} else if ( r2.isEmpty() ) {
			return new IntegerRange[] { this };
		} else {
			final int minMax = Math.min( getMax(), r2.getMax() );
			final int maxMin = Math.max( getMin(), r2.getMin() );
			final int minMin = Math.min( getMin(), r2.getMin() );
			final int maxMax = Math.max( getMax(), r2.getMax() );
			if ( maxMin - minMax > 1 ) {
				return new IntegerRange[] {
						new IntegerRange( minMin, minMax ),
						new IntegerRange( maxMin, maxMax )
				};
			} else {
				return new IntegerRange[] {
						new IntegerRange( minMin, maxMax )
				};
			}
		}
	}

}